package com.jason.www.pubsub.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import static java.util.Optional.ofNullable;
import static org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace;

/**
 * @author ybd
 * @date 17-10-30.
 */

public final class ListenerTemplate<E extends ApplicationEvent> {
	private static final Logger LOGGER = LoggerFactory.getLogger(ListenerTemplate.class);
	private List<ListenerProcessor<E>> preListenProcessorList;
	private List<ListenerProcessor<E>> afterListenProcessorList;
	private ListenerProcessor<E> onListenProcessor;
	private ApplicationListener<E> listener;

	public void process() {
		E event = getEvent();
		String simpleName = listener.getClass().getSimpleName();
		LOGGER.error(simpleName + ": success subscribe " + event.getClass()
																.getSimpleName() + " ,event data: " + event.toString());
		try {
			preListen(event);
			onListenProcessor.onListen(event);
		} catch (Exception e) {
			LOGGER.error("处理事件时发生异常：" + getFullStackTrace(e));
		} finally {
			try {
				afterListen(event);
			} catch (Exception e) {
				LOGGER.error("处理afterProcess()时发生异常：" + getFullStackTrace(e));
			}
			removeLocalEvent();
		}
		LOGGER.error(simpleName + " onListen Done");
	}

	private void removeLocalEvent() {
		EventLocalStore.remove();
	}

	@SuppressWarnings("unchecked")
	private E getEvent() {
		return (E) EventLocalStore.getEvent();
	}

	public ListenerTemplate<E> onListenProcess(ListenerProcessor<E> listenerProcessor) {
		this.onListenProcessor = listenerProcessor;
		return this;
	}

	public ListenerTemplate<E> setListener(ApplicationListener<E> listener) {
		this.listener = listener;
		return this;
	}

	public ListenerTemplate<E> setEvent(E event) {
		EventLocalStore.setEvent(event);
		return this;
	}

	public List<ListenerProcessor<E>> addPre(ListenerProcessor<E> pre) {
		if (preListenProcessorList == null) {
			preListenProcessorList = new ArrayList<>(16);
		}
		preListenProcessorList.add(pre);
		return preListenProcessorList;
	}

	public List<ListenerProcessor<E>> removePre(ListenerProcessor<E> pre) {
		preListenProcessorList.remove(pre);
		return preListenProcessorList;
	}

	public List<ListenerProcessor<E>> addAfter(ListenerProcessor<E> pre) {
		if (afterListenProcessorList == null) {
			afterListenProcessorList = new ArrayList<>(16);
		}
		afterListenProcessorList.add(pre);
		return afterListenProcessorList;
	}

	public List<ListenerProcessor<E>> removeAfter(ListenerProcessor<E> pre) {
		afterListenProcessorList.remove(pre);
		return afterListenProcessorList;
	}

	private void afterListen(E event) throws Exception {
		processWithNullable(event, afterListenProcessorList);
	}

	private void preListen(E event) throws Exception {
		processWithNullable(event, preListenProcessorList);
	}

	private void processWithNullable(E event, List<ListenerProcessor<E>> afterListenProcessorList) throws Exception {
		ofNullable(afterListenProcessorList).ifPresent(wrapException(list -> list.forEach(wrapException(e -> e.onListen(event)))));
	}

	/**
	 * 处理lambda表示式 each,
	 * @param consumer
	 * @param <T>
	 * @return
     */
	public static <T> Consumer<T> wrapException(CheckedExceptionConsumer<T> consumer) {
		return wrapException(consumer, THROWABLE_TO_RUNTIME_EXCEPTION);
	}

	/**
	 * 包装错误处理
	 * @param consumer
	 * @param handler
	 * @param <T>
	 * @return
	 */
	public static <T> Consumer<T> wrapException(CheckedExceptionConsumer<T> consumer, Consumer<Throwable> handler) {
		return (t) -> {
			try {
				consumer.accept(t);
			} catch (Throwable tx) {
				handler.accept(tx);
				throw new IllegalStateException("Exception handler must throw a RuntimeException", tx);
			}
		};
	}

	/**
	 *  处理Lambda表达式里面的错误
	 */
	public static final Consumer<Throwable> THROWABLE_TO_RUNTIME_EXCEPTION = (t) -> {
		if(t instanceof Error) {
			throw (Error)t;
		} else if(t instanceof RuntimeException) {
			throw (RuntimeException)t;
		}
	};

	@FunctionalInterface
	public interface CheckedExceptionConsumer<T> {
		void accept(T var1) throws Throwable;
	}
}
